package container

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
	"syscall"

	log "github.com/Sirupsen/logrus"
)

var (
	RUNNING             string = "running"
	STOP                string = "stopped"
	Exit                string = "exited"
	DefaultInfoLocation string = "/home/baloneo/baloneo-docker/info/%s/"
	ConfigName          string = "config.json"
	ContainerLogFile    string = "container.log"
)

// MyDockerPath 文件系统目录
var MyDockerPath = "/home/baloneo/baloneo-docker"

type ContainerInfo struct {
	Pid         string `json:"pid"`        //容器的init进程在宿主机上的 PID
	Id          string `json:"id"`         //容器Id
	Name        string `json:"name"`       //容器名
	Command     string `json:"command"`    //容器内init运行命令
	CreatedTime string `json:"createTime"` //创建时间
	Status      string `json:"status"`     //容器的状态
}

// NewParentProcess 创建一个容器进程 执行一些容器初始化操作
func NewParentProcess(tty bool, volume string, containerName string) (*exec.Cmd, *os.File) {
	// 创建一个子进程 这个子进程就是自己 这个子进程已经实现了NameSpace隔离 同时加上一个

	// 1 创建一个管道给子进程传递命令行参数
	// 2 创建子进程的时候 隔离namespace等（cgroups的配置要在子进程运行才能配置）
	readPipe, writePipe, err := NewPipe()
	if err != nil {
		log.Errorf("New pipe error %v", err)
		return nil, nil
	}

	cmd := exec.Command("/proc/self/exe", "init")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
			syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
	}
	if tty {
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	} else { // 将输出保存到文件
		dirURL := fmt.Sprintf(DefaultInfoLocation, containerName)
		if err := os.MkdirAll(dirURL, 0622); err != nil {
			log.Errorf("NewParentProcess mkdir %s error %v", dirURL, err)
			return nil, nil
		}
		stdLogFilePath := dirURL + ContainerLogFile
		stdLogFile, err := os.Create(stdLogFilePath)
		if err != nil {
			log.Errorf("NewParentProcess create file %s error %v", stdLogFilePath, err)
			return nil, nil
		}
		cmd.Stdout = stdLogFile
	}
	// exec 包内部使用了exec*系列函数，且使用了FD_CLOEXEC 导致原有进程的打开文件描述符是没有的
	cmd.ExtraFiles = []*os.File{readPipe}

	mntURL := MyDockerPath + "/mnt"
	rootURL := MyDockerPath
	// 设置容器进程的工作目录为mnt aufs文件系统的挂载点
	cmd.Dir = mntURL
	NewWorkSpace(rootURL, mntURL, volume)
	// 在创建容器之前创建aufs文件系统
	return cmd, writePipe
}

// NewWorkSpace h
func NewWorkSpace(rootURL, mntURL, volume string) {
	CreateReadOnlyLayer(rootURL)
	CreateWriteLayer(rootURL)
	CreadMountPoint(rootURL, mntURL)

	// 设置数据卷
	if volume != "" {
		volumeURLs := VolumeURLExtract(volume)
		// 校验格式
		length := len(volumeURLs)
		if length == 2 && volumeURLs[0] != "" && volumeURLs[1] != "" {
			MountVolume(rootURL, mntURL, volumeURLs)
			log.Infof("%q", volumeURLs)
		} else {
			log.Infof("Volume parameter input is not correct.")
		}
	}
}

// MountVolume 挂载数据卷
func MountVolume(rootURL, mntURL string, volumeURLs []string) {
	parentURL := volumeURLs[0] // /home/baloneo/Downloads:/root/Downloads parentURL就是冒号前的参数
	if exist, err := PathExists(parentURL); err == nil {
		if !exist {
			if err := os.Mkdir(parentURL, 0777); err != nil {
				log.Infof("创建文件夹失败 %s: %v", parentURL, err)
			}
		} else {
			fmt.Println("已经存在。", parentURL)
		}
	}

	containerURL := volumeURLs[1]
	containerVolumeURL := mntURL + containerURL
	if err := os.Mkdir(containerVolumeURL, 0777); err != nil {
		log.Infof("创建文件夹失败 %s: %v", containerVolumeURL, err)
	}

	dirs := "dirs=" + parentURL
	// 将宿主机文件夹以aufs可写的方式挂载到指定的某一个容器目录
	cmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", containerVolumeURL)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Errorf("挂载数据卷失败！ %v", err)
	}

}

// CreateReadOnlyLayer 创建一个文件夹 这个文件夹被aufs挂载的时候是只读的  也就是一个镜像
func CreateReadOnlyLayer(rootURL string) {
	busyboxURL := rootURL + "/busybox"
	busyboxTarURL := rootURL + "/busybox.tar"
	exist, err := PathExists(busyboxURL)
	if err != nil {
		log.Infof("文件不存在 %s %v", busyboxURL, err)
	}
	if exist == false {
		if err := os.Mkdir(busyboxURL, 0777); err != nil {
			log.Errorf("创建文件夹 %s 失败. %v", busyboxURL, err)
		}
		if _, err := exec.Command("tar", "-xvf", busyboxTarURL, "-C", busyboxURL).CombinedOutput(); err != nil {
			log.Errorf("解压 %s 失败: %v\n", busyboxURL, err)
		}
	}
}

// CreateWriteLayer 创建一个文件夹 以aufs挂载的时候是可以写的 也就是writeLayer文件夹 用户的写操作最后会备份到这里
func CreateWriteLayer(rootURL string) {
	writeURL := rootURL + "/writeLayer"
	if err := os.Mkdir(writeURL, 0777); err != nil {
		log.Errorf("创建文件夹失败 %s: %v", writeURL, err)
	}
}

// CreadMountPoint 创建一个aufs的挂载点 将上面两个文件夹都挂载到这个文件夹
// 用户的读写操作在mnt这个挂载点下，当卸载文件系统的时候，变化的内容会复制到可写的文件夹里面
func CreadMountPoint(rootURL, mntURL string) {
	if err := os.Mkdir(mntURL, 0777); err != nil {
		log.Errorf("创建文件夹失败 %s: %v", mntURL, err)
	}
	dirs := "dirs=" + rootURL + "/writeLayer:" + rootURL + "/busybox"      // 第一个dir默认是可写层
	cmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", mntURL) // 挂载可读可写到mnt
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	if err := cmd.Run(); err != nil {
		log.Errorf("运行挂载aufs失败！ %v", err)
	}
}

// NewPipe 创建pipe
func NewPipe() (*os.File, *os.File, error) {
	read, write, err := os.Pipe()
	if err != nil {
		return nil, nil, err
	}
	return read, write, nil
}

//Delete the AUFS filesystem while container exit
func DeleteWorkSpace(rootURL string, mntURL string, volume string) {
	if volume != "" {
		volumeURLs := VolumeURLExtract(volume)
		length := len(volumeURLs)
		if length == 2 && volumeURLs[0] != "" && volumeURLs[1] != "" {
			DeleteMountPointWithVolume(rootURL, mntURL, volumeURLs)
		} else {
			DeleteMountPoint(rootURL, mntURL)
		}
	} else {
		DeleteMountPoint(rootURL, mntURL)
	}
	DeleteWriteLayer(rootURL)
}

// DeleteMountPointWithVolume 卸载数据卷和mnt
func DeleteMountPointWithVolume(rootURL, mntURL string, volumeURLs []string) {
	containerURL := mntURL + volumeURLs[1]
	cmd := exec.Command("umount", containerURL)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Errorf("卸载数据卷失败 %v", err)
	}
	cmd = exec.Command("umount", mntURL)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Errorf("Umount 挂载点失败. %v", err)
	}

	if err := os.RemoveAll(mntURL); err != nil {
		log.Infof("删除挂载点失败 %s： %v", mntURL, err)
	}
}

func DeleteMountPoint(rootURL string, mntURL string) {
	cmd := exec.Command("umount", mntURL)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Errorf("%v", err)
	}
	if err := os.RemoveAll(mntURL); err != nil {
		log.Errorf("Remove dir %s error %v", mntURL, err)
	}
}

func DeleteWriteLayer(rootURL string) {
	writeURL := rootURL + "/writeLayer"
	if err := os.RemoveAll(writeURL); err != nil {
		log.Errorf("Remove dir %s error %v", writeURL, err)
	}
}

// PathExists 判断文件是否存在
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

// VolumeURLExtract 根据 : 分割字符串
func VolumeURLExtract(volume string) []string {
	var volumeURLs []string
	volumeURLs = strings.Split(volume, ":")
	return volumeURLs
}
